package com.gitee.wsl.scheduler.job.repository.sample

import com.gitee.wsl.concurrent.collections.ConcurrentMutableMap
import com.gitee.wsl.scheduler.job.bean.JobProgress
import com.gitee.wsl.scheduler.job.bean.JobSettings
import com.gitee.wsl.scheduler.job.bean.JobStatus
import com.gitee.wsl.scheduler.job.bean.ScheduledJob
import com.gitee.wsl.scheduler.job.repository.JobRepository
import com.gitee.wsl.struct.generator.randomUUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.take
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

@OptIn(ExperimentalTime::class)
class MemoryJobRepository  constructor(val clock: Clock = Clock.System): JobRepository {
    private val map = ConcurrentMutableMap<String, ScheduledJob>()
    private fun newId(): String = randomUUID()

    override suspend fun exist(jobId: String): Boolean = map.values.find { it.settings.id == jobId } != null

    @OptIn(ExperimentalTime::class, ExperimentalUuidApi::class)
    override suspend fun save(jobSettings: JobSettings, runAt: Instant?): ScheduledJob {
        if (exist(jobSettings.id)) {
            throw IllegalArgumentException("Job[${jobSettings.id}] does already exist")
        }
        val now = clock.now()
        val sj = ScheduledJob(newId(), JobStatus.CREATED, runAt, null, 0, null, now, now, jobSettings, JobProgress(0))
        map[sj.id] = sj
        return sj
    }

    override suspend fun get(id: String): ScheduledJob? = map[id]

    @OptIn(ExperimentalUuidApi::class, ExperimentalTime::class)
    override suspend fun update(id: String, oldKjobId: Uuid?, kjobId: Uuid?, status: JobStatus, statusMessage: String?, retries: Int): Boolean {
        val sj = map[id]
        return if (sj == null || sj.kjobId != oldKjobId) {
            false
        } else {
            val newJe = sj.copy(
                kjobId = kjobId,
                status = status,
                statusMessage = statusMessage,
                retries = retries,
                updatedAt = clock.now()
            )
            map[id] = newJe
            true
        }
    }

    @OptIn(ExperimentalUuidApi::class)
    override suspend fun reset(id: String, oldKjobId: Uuid?): Boolean {
        val sj = map[id]
        return if (sj == null || sj.kjobId != oldKjobId) {
            false
        } else {
            val newJe = sj.copy(
                status = JobStatus.CREATED,
                statusMessage = null,
                kjobId = null,
                updatedAt = clock.now(),
                progress = JobProgress(0)
            )
            map[id] = newJe
            true
        }
    }

    @OptIn(ExperimentalUuidApi::class)
    override suspend fun startProgress(id: String): Boolean {
        val sj = map[id]
        return if (sj == null) {
            false
        } else {
            map[id] = sj.copy(
                updatedAt = clock.now(),
                progress = sj.progress.copy(startedAt = clock.now())
            )
            true
        }
    }

    @OptIn(ExperimentalUuidApi::class)
    override suspend fun completeProgress(id: String): Boolean {
        val sj = map[id]
        return if (sj == null) {
            false
        } else {
            map[id] = sj.copy(
                updatedAt = clock.now(),
                progress = sj.progress.copy(completedAt = clock.now())
            )
            true
        }
    }

    @OptIn(ExperimentalUuidApi::class)
    override suspend fun stepProgress(id: String, step: Long): Boolean {
        val sj = map[id]
        return if (sj == null) {
            false
        } else {
            map[id] = sj.copy(
                updatedAt = clock.now(),
                progress = sj.progress.copy(step = sj.progress.step + step)
            )
            true
        }
    }

    @OptIn(ExperimentalUuidApi::class)
    override suspend fun setProgressMax(id: String, max: Long): Boolean {
        val sj = map[id]
        return if (sj == null) {
            false
        } else {
            map[id] = sj.copy(
                updatedAt = clock.now(),
                progress = sj.progress.copy(max = max)
            )
            true
        }
    }

    @ExperimentalCoroutinesApi
    override suspend fun findNext(names: Set<String>, status: Set<JobStatus>, limit: Int): Flow<ScheduledJob> {
        val filter1: (ScheduledJob) -> Boolean = { if (names.isEmpty()) true else names.contains(it.settings.name) }
        val filter2: (ScheduledJob) -> Boolean = { status.contains(it.status) }
        return map.values.sortedBy { it.createdAt.epochSeconds }.asFlow().filter { filter1(it) && filter2(it) }.take(limit)
    }

    internal fun deleteAll() {
        map.clear()
    }

    companion object{
        val DEFAULT = MemoryJobRepository()
    }

}

val JobRepository.Companion.Memory get() = MemoryJobRepository.DEFAULT